package com.tarena.redis.demo;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.*;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 利用测试框架
 * 加载RedisApplication启动类
 * 运行一个spring容器
 * 在这个容器中可以获取一个现成对象RedisTemplate
 */
@SpringBootTest
public class RedisTestApplication {
    @Autowired
    private RedisTemplate<String,String> redisTemplate;
    /**
     * 操作 一下localhost:6379的redis 存储一个key age:18
     * 理论上来说 了解了如何set 就能自己寻找方法 get incr等 lpush rpush zadd sadd 等等
     */
    @Test
    public void test(){
        //1.客户端对象现在是一个spring-data-redis对底层api做的上成封装 template模版
        //模版对象本身可以调用一部分redis的功能命令 比如 keys exists 不关心数据类型
        Set<String> keys = redisTemplate.keys("*");
        System.out.println(keys);
        Boolean nameEx = redisTemplate.hasKey("name");//就是exists 判断你给的key值是否存在
        Boolean abcEx = redisTemplate.hasKey("abc");
        System.out.println("name:"+nameEx);
        System.out.println("abc:"+abcEx);
        //如果我想要操作指定类型的命令 比如 string set get incr decr
        redisTemplate.opsForValue().set("age","18");//set age 18
    }
    //1. 使用客户端 template 操作 string类型的属性
    @Test
    public void stringTest(){
        //模版对象中,操作String类型的redis数据 需要获取 子对象ValueOptions 两个泛型表示
        //数据的key值 和value值都是String序列化方式
        ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
        //1.调用 set
        //1.1 set name wangcuihua 设置永久数据 name=王翠花
        opsForValue.set("name","王翠花");
        System.out.println("新增name=王翠花");
        //1.2 set age 18 EX 10     (set age 18 ; expire age 10) 设置了age=18 存活时间10秒钟
        opsForValue.set("age","18",10, TimeUnit.SECONDS);
        System.out.println("新增age=18 超时10秒钟");
        //2.调用 get
        String ageValue = opsForValue.get("age");
        System.out.println("获取age:"+ageValue);
        //opsForValue.get("name",0,10);//截取name的0-10位
        //3.incr decr incrby decrby  方法返回值就是自增 自减之后的数字 如果对着一个不存在key做
        //操作 参考值从0开始
        Long incr = opsForValue.increment("num");
        System.out.println("对num自增结果:"+incr);
        incr = opsForValue.increment("num", 5);
        System.out.println("对num再次自增5结果:"+incr);
    }

    //2.测试hash类型的数据结构
    @Test
    public void hashTest(){
        //依然要先获取 模版对象子对象 操作hash HashOperations
        //key field value 三个值都是String序列化
        HashOperations<String, String, String> opsForHash = redisTemplate.opsForHash();
        //1.hset user age 10
        opsForHash.put("user","age","10");
        opsForHash.put("user","name","王翠花");
        //2.hget user age ;hget user name
        String ageValue = opsForHash.get("user", "age");
        System.out.println("在hash类型user中拿到age属性值:"+ageValue);
        String nameValue = opsForHash.get("user", "name");
        System.out.println("在hash类型user中拿到name属性值:"+nameValue);
        //3.hkeys user
        Set<String> keys = opsForHash.keys("user");
        System.out.println("获取到hash类型user的keys:"+keys);
        //4.hvals user
        List<String> values = opsForHash.values("user");
        System.out.println("获取到hash类型user的values:"+values);
        //5.客户端可以直接存放map数据 自动转化成 hash类型结果
        //一旦客户端程序中一次性要存放N个 field和value值 允许我们一次完成,不需要调用hset多次
        Map<String,String> mapValue=new HashMap<>();
        mapValue.put("name","刘首富");
        mapValue.put("age","18");
        mapValue.put("gender","male");
        mapValue.put("school","清华");
        opsForHash.putAll("user2",mapValue);
        Set<String> keys2 = opsForHash.keys("user2");
        System.out.println("获取到hash类型user2的keys:"+keys2);
        List<String> values2 = opsForHash.values("user2");
        System.out.println("获取到hash类型user2的values:"+values2);
    }
    //3.List类型
    @Test
    public void listTest(){
        //取模版子对象ListOperations
        ListOperations<String, String> opsForList = redisTemplate.opsForList();
        //1. lpush rpush list01 10 20 30 40 50...
        opsForList.leftPush("list01","10");
        List<String> list01=new ArrayList<>();
        list01.add("20");
        list01.add("30");
        opsForList.leftPushAll("list01",list01);
        opsForList.rightPush("list01","40");
        //2. lrange list01 0 -1
        list01 = opsForList.range("list01", 0, -1);
        System.out.println("获取到list01:"+list01);
        //3. lset list01 0 "一百"
        opsForList.set("list01",0,"一百");
        //4. rpop lpop 冒泡删除
        String rpopValue = opsForList.rightPop("list01");//从尾部冒泡
        System.out.println("从list01删除尾部元素:"+rpopValue);
        String lpopValue = opsForList.leftPop("list01");
        System.out.println("从list01删除头部元素:"+lpopValue);
    }
    //5 Geo 地理位置数据(这套api方法封装和使用不是那么的方便)
    @Test
    public void geoTest(){
        GeoOperations<String, String> opsForGeo = redisTemplate.opsForGeo();
        //1. geoadd stations 116.999,39.127 "北京回龙观站"
        //经度在redis中称为x坐标 维度相对就是y坐标
        opsForGeo.add("stations",new Point(116.999,39.127),"北京回龙观站");
        opsForGeo.add("stations",new Point(117.999,40.127),"北京昌平站");
        opsForGeo.add("stations",new Point(118.999,41.127),"北京中关村站");
        //2. 可以获取在集合中 某2个元素的地理距离distance
        //2.1 使用默认参数 获取距离
        Distance distance = opsForGeo.distance("stations", "北京回龙观站", "北京中关村站");
        //distance返回值 封装了底层命令调用的返回结果 包括 单位 包括距离数字
        String unit = distance.getUnit();
        System.out.println("距离单位:"+unit);
        Double distanceValue = distance.getValue();
        System.out.println("距离数字:"+distanceValue);
        //2.2 如果想要在 查询距离的时候 添加一些选项参数 比如 自定义单位 需要封装metric
        RedisGeoCommands.DistanceUnit metric = RedisGeoCommands.DistanceUnit.KILOMETERS;
        distance = opsForGeo.distance("stations", "北京昌平站", "北京回龙观站", metric);
        unit = distance.getUnit();
        System.out.println("距离单位:"+unit);
        distanceValue = distance.getValue();
        System.out.println("距离数字:"+distanceValue);
        //3. 根据某个中心点 查询半径距离内容 当前集合覆盖的所有元素 georadius
        //georadius key lon lat radius unit
        //circle包括2个参数 一个是中心点 半径
        Point point=new Point(116.000,39.000);
        Distance radius=new Distance(100,RedisGeoCommands.DistanceUnit.KILOMETERS);
        //携带WITHCOORD 展示元素绑定位置  携带WITHDIST 展示元素和中心点的距离
        RedisGeoCommands.GeoRadiusCommandArgs args=
                RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs();
        //携带WITHCOORD
        args.includeCoordinates();
        //携带一个dist
        args.includeDistance();
        GeoResults<RedisGeoCommands.GeoLocation<String>> stations =
                opsForGeo.radius("stations", new Circle(point, radius),args);
        //6.对于查询范围内的元素的一些参数,可以在api中定义声明
        //还需要解析 返回值
        for (GeoResult<RedisGeoCommands.GeoLocation<String>> result : stations) {
            //将查询结果 所包含的内容做一个简单打印,在继续向下考究这个api方法
            //取出 当初存放元素 值 北京回龙观 北京昌平 北京中关村
            String itemName = result.getContent().getName();
            //取出 这个元素绑定的point
            Point itemPoint = result.getContent().getPoint();
            //取距离 单位
            String itemUnit = result.getDistance().getUnit();
            //取距离值
            Double itemValue = result.getDistance().getValue();
            System.out.println("元素值:"+itemName);
            System.out.println("元素point:"+itemPoint);
            System.out.println("元素距离中心点:"+itemValue+itemUnit);
            //可以对上面的3个值调整 作为一个接口 的返回值 在页面使用
        }
    }





















}
